package com.xx;

import com.xx.ViewInfo.FileViewBar;
import com.xx.ViewInfo.FileViewItem;
import com.xx.ViewInfo.HdfsView;
import com.xx.hdfs.HdfsInfo;
import com.xx.hdfs.HdfsLog;
import com.xx.hdfsFx.promptStage.ChangePathPrompt;
import com.xx.hdfsFx.promptStage.ErrorPrompt;
import com.xx.landing.Landing;
import com.xx.menu.AddContextMenu;
import com.xx.menu.AddMenu;
import javafx.application.Application;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.*;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;

import javax.rmi.CORBA.Util;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import static com.xx.util.Util.getResourceUrl;


public class FXHdfsApp extends Application {
    public HdfsLog log;
    HdfsInfo hdfs;
    private StackPane apT;
    private StackPane apC;
    private StackPane apL;

    private StackPane apR;

    private final TreeView<HdfsView> view = new TreeView<>();
    //    treeView的图片
    private final Image fileI = new Image(getResourceUrl("/texture/file.png"));
    private final Image dirI = new Image(getResourceUrl("/texture/dir.png"));
    private final Image onI = new Image(getResourceUrl("/texture/on.png"));
    private final Image offI = new Image(getResourceUrl("/texture/off.png"));
    //    fileViewItem的图片
    private final Image FIconI = new Image(getResourceUrl("/texture/fileIcon.png"));
    private final Image x = new Image(getResourceUrl("/texture/x.png"));
    private final Image xIn = new Image(getResourceUrl("/texture/xIn.png"));
    private final Image line = new Image(getResourceUrl("/texture/line.png"));
    //    textArea 的图片
    private final Image TeditI = new Image(getResourceUrl("/texture/edit.png"));
    private final Image TSaveI = new Image(getResourceUrl("/texture/save.png"));
    private final Image TAllI = new Image(getResourceUrl("/texture/selectAll.png"));
    private final Image TcopyI = new Image(getResourceUrl("/texture/copy.png"));
    private final Image TstickupI = new Image(getResourceUrl("/texture/paste.png"));
    private final Image TquitI = new Image(getResourceUrl("/texture/quit.png"));
    private final Image TcutI = new Image(getResourceUrl("/texture/cut.png"));
    private Set<String> expendPath = new HashSet<>();
    private AddContextMenu treeCm;
    private FileViewBar fileViewBar;
    private HBox treeItemIfo;
    private AnchorPane root;
    private Map map;
    private Landing landing;

    @Override
    public void start(Stage stage) {
        Stage app = new Stage();
        landing = new Landing(app);
        landing.setRun(() -> {
            try {
                map = landing.getNowMap();
                ArrayList<String> logs;
                if (map.containsKey("logInfo")) logs = (ArrayList<String>) map.get("logInfo");
                else logs = new ArrayList<>();
                log = new HdfsLog();
                log.initView(logs);

                log.addLog(1, "登入FX HDFS.");
                hdfs = new HdfsInfo(map);
                Scene scene = mainScene();
                scene.getStylesheets().add(Objects.requireNonNull(
                        Util.class.getResource("/css/hdfsApp.css")).toExternalForm());
                stage.setScene(scene);
                stage.setMaximized(true);
                stage.setWidth(scene.getWidth());
                stage.setHeight(scene.getHeight());
                stage.toFront();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            stage.setTitle("FXHdfs");
            stage.getIcons().add(new Image(getResourceUrl("/texture/icon.png")));
            stage.show();
            app.close();
        });

    }

    @Override
    public void stop() {
        if (log != null) log.addLog(1, "退出FX  HDFS.");
        if (hdfs != null) hdfs.demo.close();
        if (map != null && log != null) log.save(map);
        landing.save();
    }

    public Scene mainScene() throws IOException {

//        底层布局是一个AnchorPane
        root = new AnchorPane();
//        布局方式.两个hbox占位.
        VBox v = new VBox();
        root.getChildren().addAll(v, new HBox(), new HBox());

        apT = new StackPane();
        apT.setMinSize(0, 0);
        apC = new StackPane();
        apC.setMinSize(0, 0);
        apC.setStyle("-fx-background-color:" +
                "linear-gradient(from 0.0% 0.0% to 100.0% 0.0%, #9becfa33 0.0%, #65c9f733 50.0%, #0256d433 100.0%);");
        apL = new StackPane(view);
        apL.setMinSize(0, 0);
        apR = new StackPane();
        apR.setMinSize(0, 0);
        apR.setPrefSize(0, 0);

        addFileView();
        addMenuBar();
        addView();

        treeCm = new AddContextMenu(view, hdfs, this);

        HBox hb = new HBox();

        SplitPane splitV = new SplitPane();
        splitV.setOrientation(Orientation.VERTICAL);
        SplitPane splitH = new SplitPane(apL, apC);
        splitH.setDividerPosition(0, 0.1);
        splitV.getItems().addAll(new StackPane(splitH), log.sp);
        splitV.setDividerPosition(0, 0.9);
        TabPane itemInfo = getLeftInfoPane(splitV, splitH);
        hb.getChildren().addAll(splitV, itemInfo);
        v.getChildren().addAll(apT, hb);

        Scene scene = new Scene(root, 1200, 800);
        scene.widthProperty().addListener((ob, ov, nv) -> splitV.setPrefWidth((double) nv - itemInfo.getWidth()));
        scene.heightProperty().addListener((ob, ov, nv) -> splitV.setPrefHeight((double) nv - apT.getHeight()));
        return scene;
    }

    private TabPane getLeftInfoPane(SplitPane splitV, SplitPane splitH) {
        TabPane tabPane = new TabPane();
        tabPane.setPrefWidth(50);
        tabPane.setSide(Side.RIGHT);
        tabPane.setRotateGraphic(false);
        tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);

        Tab tableViewTab = new Tab();
        Image tableView = new Image(getResourceUrl("/texture/moreInfo.png"));
        ImageView tableViewIcon = new ImageView(tableView);

        ColorAdjust tabAdjust = new ColorAdjust();
        tabAdjust.setBrightness(0);
        tabAdjust.setContrast(0);
        tabAdjust.setHue(0);
        tabAdjust.setSaturation(0);

        tableViewIcon.setPreserveRatio(true);
        tableViewIcon.setFitWidth(50);
        tableViewIcon.setEffect(tabAdjust);
        Label table = new Label();
        table.setPrefSize(tableViewIcon.getFitWidth(), tableViewIcon.getFitHeight());
        table.setGraphic(tableViewIcon);
        tableViewTab.setGraphic(table);
        AtomicReference<Double> aprDivider = new AtomicReference<>(0.8);
        tableViewTab.setStyle("-fx-background-color: #9c9c9c66");
        table.setOnMouseClicked(event -> {
            if (splitH.getItems().contains(apR)) {
                tabAdjust.setBrightness(0);
                tabAdjust.setContrast(0);
                tabAdjust.setHue(0);
                tabAdjust.setSaturation(0);
                aprDivider.set(splitH.getDividers().get(1).getPosition());
                splitH.getItems().remove(apR);
                tabAdjust.setBrightness(1);
                tableViewTab.setStyle("-fx-background-color: #9c9c9c66");
            } else {
                splitH.getItems().add(apR);
                splitH.setDividerPosition(1, aprDivider.get());
                tabAdjust.setBrightness(-0.3);
                tabAdjust.setContrast(1);
                tabAdjust.setHue(0);
                tabAdjust.setSaturation(0);
                tabAdjust.setBrightness(0);
                tableViewTab.setStyle("-fx-background-color: #79df3999");
            }
        });
        table.setOnMouseEntered(event -> {
            if (!splitH.getItems().contains(apR)) {
                tabAdjust.setBrightness(0.2);
                tabAdjust.setContrast(1);
                tabAdjust.setHue(-3 / 14.0);
                tabAdjust.setSaturation(0);
            }
        });
        table.setOnMouseExited(event -> {
            if (!apR.getChildren().contains(apR)) {
                tabAdjust.setBrightness(0);
                tabAdjust.setContrast(0);
                tabAdjust.setHue(0);
                tabAdjust.setSaturation(0);
            }
        });

        Tab logViewTab = new Tab();

        ColorAdjust logAdjust = new ColorAdjust();
        logAdjust.setBrightness(-0.3);
        logAdjust.setContrast(1);
        logAdjust.setHue(0);
        logAdjust.setSaturation(0);

        Image logI = new Image(getResourceUrl("/texture/log.png"));
        ImageView logViewIcon = new ImageView(logI);
        logViewIcon.setPreserveRatio(true);
        logViewIcon.setFitWidth(50);
        logViewIcon.setEffect(logAdjust);
        Label logView = new Label();
        logView.setPrefSize(logViewIcon.getFitWidth(), logViewIcon.getFitHeight());
        logView.setGraphic(logViewIcon);
        logViewTab.setGraphic(logView);
        AtomicReference<Double> logViewDivider = new AtomicReference<>((double) 1);
        log.sp.managedProperty().bind(log.sp.visibleProperty());
        splitV.setDividerPosition(0, 0.5);
        logViewTab.setStyle("-fx-background-color: #79df3999");
        logView.setOnMouseClicked(event -> {
            if (splitV.getItems().contains(log.sp)) {
                logViewDivider.set(splitV.getDividers().get(0).getPosition());
                splitV.getItems().remove(log.sp);
                logAdjust.setBrightness(0);
                logAdjust.setContrast(0);
                logAdjust.setHue(0);
                logAdjust.setSaturation(0);
                logViewTab.setStyle("-fx-background-color: #9c9c9c66");
            } else {
                splitV.getItems().add(log.sp);
                splitV.setDividerPosition(0, logViewDivider.get());
                logAdjust.setBrightness(-0.3);
                logAdjust.setContrast(1);
                logAdjust.setHue(0);
                logAdjust.setSaturation(0);
                logViewTab.setStyle("-fx-background-color: #79df3999");
            }
        });
        logView.setOnMouseEntered(event -> {
            if (!splitV.getItems().contains(log.sp)) {
                logAdjust.setBrightness(0.2);
                logAdjust.setContrast(1);
                logAdjust.setHue(-3 / 14.0);
                logAdjust.setSaturation(0);
            }
        });
        logView.setOnMouseExited(event -> {
            if (!splitV.getItems().contains(log.sp)) {
                logAdjust.setBrightness(0);
                logAdjust.setContrast(0);
                logAdjust.setHue(0);
                logAdjust.setSaturation(0);
            }
        });

        tabPane.getTabs().addAll(tableViewTab, logViewTab);
        return tabPane;
    }

    public void addFileView() {
        fileViewBar = new FileViewBar();
        VBox text = new VBox(40);
        text.setMinSize(0, 0);
        text.setAlignment(Pos.CENTER);
        text.getChildren().addAll(new Text("FX HDFS 管理器"), new Text("双击文件打开"), new Text("考虑到内存问题,文件超过一定大小就只提供预览功能," +
                "更多操作请下载到本地"));
        apC.getChildren().addAll(text, fileViewBar);
    }

    public void addMenuBar() {
        new AddMenu(apT);
    }

    public void addView() throws IOException {
        Set<FileStatus> set = hdfs.getSet();
        Map<String, HdfsView> map = new HashMap<>();
        HdfsView hdfsRootDir = new HdfsView(hdfs.demo.getFileStatus(new Path("/")), map, apR, expendPath);
        TreeItem<HdfsView> item = hdfsRootDir.item;
        set.forEach(e -> new HdfsView(e, map, apR, expendPath));
        view.setRoot(item);
//        不重复加载.
        view.getSelectionModel().selectedItemProperty().addListener((ob, ov, nv) -> {
            if (nv == null) return;
            nv.getValue().onChoice();
        });
        view.setOnMouseClicked(e -> {
//            不重复加载
            if (e.getClickCount() == 2 && e.getButton().equals(MouseButton.PRIMARY)) {
                TreeItem<HdfsView> clicks = view.getTreeItem(view.getSelectionModel().getSelectedIndex());
                if (clicks == null) return;
                if (clicks.getValue().status.isFile()) {
//                    检查该文件是否已经打开.
                    FilteredList<Tab> filtered = fileViewBar.getTabs()
                            .filtered(node -> ((FileViewItem) node).hdfsView.path
                                    .equals(clicks.getValue().path));
                    if (filtered.isEmpty())
                        fileViewBar.addItem(new FileViewItem(root, clicks.getValue(), hdfs.demo, xIn, x, line, FIconI,
                                TeditI, TSaveI, TAllI, TcopyI, TstickupI, TquitI, TcutI, log));
                    else fileViewBar.setChoiceItem((FileViewItem) filtered.get(0));
                }

            }
        });
        map.values().forEach(e -> {
            if (e.status.getPath().getParent() != null)
                map.get(e.status.getPath().getParent().toString()).item.getChildren().add(e.item);
        });
        map.values().forEach(e -> {
            e.initTableView();
            e.refreshExpendInfo();
        });
        map.values().forEach(e -> e.expendLeaf = true);
        viewCell();

    }

    private void viewCell() {
        view.setCellFactory(new Callback<TreeView<HdfsView>, TreeCell<HdfsView>>() {
            @Override
            public TreeCell<HdfsView> call(TreeView<HdfsView> param) {
                TreeCell<HdfsView> treeCell = new TreeCell<HdfsView>() {
                    @Override
                    protected void updateItem(HdfsView item, boolean empty) {
                        super.updateItem(item, empty);
                        if (!empty) {
                            ImageView file = new ImageView(fileI);
                            file.setPreserveRatio(true);
                            file.setFitWidth(25);
                            ImageView dir = new ImageView(dirI);
                            dir.setPreserveRatio(true);
                            dir.setFitWidth(35);
                            HBox hBox = new HBox(10);
                            hBox.setTranslateX(10);
                            hBox.setAlignment(Pos.BASELINE_LEFT);
                            this.setDisclosureNode(null);
                            hBox.getChildren().clear();
                            Boolean b = item.status.isDirectory() ? hBox.getChildren().add(dir) :
                                    hBox.getChildren().add(file);
                            hBox.getChildren().add(new Text(item.status.getPath().getName()));
                            this.setGraphic(hBox);
                            if (this.getTreeItem().isExpanded()) {
                                ImageView on = new ImageView(onI);
                                on.setPreserveRatio(true);
                                on.setFitWidth(30);
                                this.setDisclosureNode(on);
                            } else {
                                ImageView off = new ImageView(offI);
                                off.setPreserveRatio(true);
                                off.setFitWidth(30);
                                this.setDisclosureNode(off);
                            }
                        } else {
                            this.setGraphic(null);
                        }
                    }
                };

                treeItemDragAction(treeCell, dirI, fileI);

                return treeCell;
            }
        });
    }

    //拖拽事件
    private void treeItemDragAction(TreeCell<HdfsView> treeCell, Image dirI, Image fileI) {
//        实现在treeItem上停留显示数据的功能.
        theMouseDisplaysInformation(treeCell);
//        实现拖动treeItem功能.
        treeCellDrop(treeCell, dirI, fileI);
    }

    private void treeCellDrop(TreeCell<HdfsView> treeCell, Image dirI, Image fileI) {
//        最先调用:0
        treeCell.setOnDragDetected(event -> TreeCellDragDetected(treeCell, dirI, fileI));
//        1
//        拖拽在哪一个item,focus就在哪里
        treeCell.setOnDragEntered(event -> treeCell.getTreeView().getFocusModel().focus(treeCell.getIndex()
        ));
//        2
        treeCell.setOnDragOver(event -> treeCellDragOver(treeCell, event));
//        3
//            让event的数据传递给setOnDragDone
        treeCell.setOnDragDropped(event -> event.setDropCompleted(true));
//        4
        treeCell.setOnDragDone(event -> {
            //                空白直接return.
            if (treeCell.getTreeItem() == null ||
                    event.getTransferMode() == null ||
                    !event.getTransferMode().equals(TransferMode.MOVE)) return;
            moveItemAction(treeCell, event);
        });
//        这个事件是为了实现当拖拽出view上时,呈现出拖拽前的treeItem未选中的样式.
        view.setOnDragExited(event -> {
            if (event.getX() < 0 || event.getX() > apL.getWidth()
                    || event.getY() < 0 || event.getY() > apL.getHeight())
                treeCell.getTreeView().getFocusModel().focus(-1);
        });
    }

    private void treeCellDragOver(TreeCell<HdfsView> treeCell, DragEvent event) {
        //            空也不能转移.
        if (treeCell.getItem() != null && event.getDragboard().hasString()) {
            TreeItem<HdfsView> oldTreeItemDir = view.getSelectionModel().getSelectedItems().get(0).getParent();
            TreeItem<HdfsView> newTreeItemDir =
                    treeCell.getTreeView().getFocusModel().getFocusedItem().getValue().status.isDirectory() ?
                            treeCell.getTreeView().getFocusModel().getFocusedItem()
                            : treeCell.getTreeView().getFocusModel().getFocusedItem().getParent();
            //               相同目录直接return.
            if (event.getGestureSource() != treeCell && !oldTreeItemDir.equals(newTreeItemDir))
                event.acceptTransferModes(TransferMode.MOVE);
            else treeCell.getTreeView().getFocusModel().focus(-1);
        }
        event.consume();
    }

    private void TreeCellDragDetected(TreeCell<HdfsView> treeCell, Image dirI, Image fileI) {
        if (treeCell.getItem() != null) {
            Dragboard db = treeCell.startDragAndDrop(TransferMode.MOVE);
//            getItem()是拖动的那一个treeItem.
            ClipboardContent content = new ClipboardContent();
            content.putString(treeCell.getItem().status.getPath().getName());
            ImageView iv = treeCell.getItem().status.isDirectory() ? new ImageView(dirI) : new ImageView(fileI);
            iv.setPreserveRatio(true);
            iv.setFitWidth(treeCell.getItem().status.isDirectory() ? 45 : 30);

            HBox dbHb = new HBox(10);
            dbHb.setAlignment(Pos.CENTER);
            Text t = new Text(treeCell.getItem().status.getPath().getName());
            t.setFont(new Font(30));
            t.setFill(treeCell.getItem().status.isDirectory() ? Color.RED : Color.BLUE);
            if (treeCell.getTreeItem().getParent() == null) t.setFill(Color.PINK);
            dbHb.getChildren().addAll(iv, t);
            WritableImage wi = new WritableImage(200, 40);
            dbHb.snapshot(new SnapshotParameters(), wi);

            db.setDragView(wi);
            db.setContent(content);

        }
    }

    private void theMouseDisplaysInformation(TreeCell<HdfsView> treeCell) {
        treeCell.setOnMouseEntered(event -> {
            treeCell.getTreeView().getFocusModel().focus(treeCell.getIndex());
            event.consume();
            if (treeCell.getItem() == null) return;
            treeItemIfo = treeCell.getItem().viewInfo;
            if (treeItemIfo == null) return;
            root.getChildren().set(1, treeItemIfo);
            treeItemIfo.setLayoutX(event.getSceneX() + 20);
            treeItemIfo.setLayoutY(event.getSceneY());
        });
        treeCell.setOnMouseExited(event -> {
            root.getChildren().set(1, new HBox());
            event.consume();
        });
        treeCell.setOnMouseMoved(event -> {
            if (treeItemIfo != null) {
                treeItemIfo.setLayoutX(event.getSceneX() + 20);
                treeItemIfo.setLayoutY(event.getSceneY());
            }
            event.consume();
        });
        view.setOnMouseExited(event -> {
            treeCell.getTreeView().getFocusModel().focus(-1);
            root.getChildren().set(1, new HBox());
            event.consume();
        });
    }

    private void moveItemAction(TreeCell<HdfsView> treeCell, DragEvent event) {
        if (treeCell.getTreeItem().getParent() == null || treeCell.getItem().status == null
                || Objects.equals(treeCell.getItem().status.getPath().getName(), "")) {
            ErrorPrompt errorPrompt = new ErrorPrompt("警告", "你正在试图更改根目录的路径!!!");
            errorPrompt.showStage();
            log.addLog(3, "试图更改根目录路径.");
            return;
        }
        if (treeCell.getItem().status.isDirectory() && (treeCell.getTreeItem().getParent().getParent() == null
                || Objects.equals(treeCell.getTreeItem().getParent().getValue().status.getPath().getName(), "/"))) {
            ;
            ErrorPrompt errorPrompt = new ErrorPrompt("你正在试图更改根目录下的直属目录:" + treeCell.getItem().status.getPath().getName() +
                    "FXHdfs" +
                    "已经关闭了该功能" +
                    ",請自行使用命令更改.");
            log.addLog(2, "试图更改根目录下的直属目录路径,防止误操作,FX HDFS关闭了该功能");
            errorPrompt.showStage();
            return;
        }
        TreeItem<HdfsView> focusedItem = treeCell.getTreeView().getFocusModel().getFocusedItem();
        if (!event.getTransferMode().equals(TransferMode.MOVE) || focusedItem.getValue() == null) return;
        TreeItem<HdfsView> focusedDir = focusedItem.getValue().status.isDirectory() ?
                focusedItem :
                focusedItem.getParent();
        Path newPath = new Path(focusedDir.getValue().status.getPath() + "/" + treeCell.getItem().status.getPath().getName());
        ChangePathPrompt changePathPrompt = new ChangePathPrompt(treeCell.getItem().status.getPath().getName(),
                "/" + focusedDir.getValue().status.getPath());
        changePathPrompt.button.setOnAction(event1 -> {
            changePathPrompt.closeStage();
            if (!hdfs.demo.rename(treeCell.getItem().status.getPath(), newPath)) {
                ErrorPrompt errorPrompt =
                        new ErrorPrompt("移动" + (treeCell.getItem().status.isDirectory() ? "目录:" : "文件:") + treeCell.getItem().status.getPath().getName() + "失败", "出现同名文件");
                errorPrompt.showStage();
                log.addLog(3, "移动" + (treeCell.getItem().status.isDirectory() ? "目录:" : "文件:/")
                        + treeCell.getItem().status.getPath() + "失败");
            } else {
                TreeItem<HdfsView> nowTreeItem = treeCell.getTreeItem();
                String old = nowTreeItem.getValue().status.getPath().toString();
                TreeItem<HdfsView> parentTreeItem = treeCell.getTreeItem().getParent();
                parentTreeItem.getChildren().remove(nowTreeItem);
                parentTreeItem.getValue().initTableView();
                nowTreeItem.getValue().refreshItem(hdfs, newPath);
                focusedDir.getChildren().add(nowTreeItem);
                focusedItem.getValue().initTableView();
                view.refresh();
                log.addLog(1, "移动" + (treeCell.getItem().status.isDirectory() ? "目录:" : "文件:/") +
                        old + "到/" + focusedDir.getValue().status.getPath() + "目录下.");
            }
        });
        changePathPrompt.showStage();
    }

    public void refresh() throws IOException {
        int select = view.getSelectionModel().getSelectedIndex();
        int focus = view.getFocusModel().getFocusedIndex();
        view.setContextMenu(null);
        apR.getChildren().clear();
        addView();
        System.gc();
        view.scrollTo(select + 6);
        view.getFocusModel().focus(focus);
        view.getSelectionModel().select(select);
        view.setContextMenu(treeCm.cm);
        treeCm.tree = view;
    }
}
